import os.path

from flask import current_app, request
from flask_restx import abort, reqparse

from tools.basic_tools import Tools
from app.api.v1.webhook.webhook import WebHookManager
from app.common.openstack_manager import OpenstackManager
from app.models import Image, ContainerFormat
from tools import db, ops
from utils.utils import base_detail

from .base_view import AdminValidators

# 镜像保存位置
IMAGE_BASE_PATH = "/var/img"


class FlavorsListAPI(AdminValidators):
    """
    虚拟机flavors
    since: 22.3.2
    """
    @property
    def load_cloud_object(self):
        return WebHookManager()

    def get(self):
        flavors = self.load_cloud_object.get_flavors()
        data = [
            {
                "id": flavor["uuid"],
                "name": flavor["name"],
                "ram": flavor["memorySize"] / 1024 / 1024,
                "cpu": flavor["cpuNum"],
            }
            for flavor in flavors
        ]
        return {"code": 200, "data": data}


class VmImagesAPI(AdminValidators):
    """
    虚机镜像列表/分片上传
    since: 22.2.31
    """

    def get(self):
        params = request.args
        image_name = params.get("imageName")
        page = params.get("page", 1)
        page_size = params.get("pageSize", 10)

        try:
            page = int(page)
            page_size = int(page_size)
        except:  # noqa
            page = 1
            page_size = 10

        filters = [Image.status != '0']
        if image_name:
            filters.append(Image.image_name.contains(image_name))

        pagination = (
            Image.query.filter(*filters)
            .order_by(Image.id.desc())
            .paginate(page, page_size, error_out=False)
        )
        data = {"code": 200, "current_page": page, "data": []}

        for image in pagination.items:
            data['data'].append(image.to_dict())
        data = base_detail(data, pagination)
        return data

    def post(self):
        file = request.files.get("file")
        parser = reqparse.RequestParser(bundle_errors=False)
        parser.add_argument("filename", help="镜像名不能为空", required=True, location='form')
        parser.add_argument("ext", help="文件后缀不能为空", required=True, location='form')
        parser.add_argument("os_type", help="请选择系统类型", required=True, location='form')
        parser.add_argument(
            "os_type", choices=("linux", "windows"),
            help="操作系统范围错误: {error_msg}", required=True, location='form'
        )
        parser.add_argument("identifier", help="文件唯一标识符未提供", required=True, location='form')  # 文件唯一标识符
        parser.add_argument(
            "chunkNumber", help="当前分片序号未提供", type=int, default=1, location='form'
        )  # 当前分片序号
        parser.add_argument(
            "is_done", help="是否上传完成", type=int, default=0, location='form'
        )  # 是否上传完成 0=未完成 1=完成
        parser.add_argument(
            "is_container", choices=(0, 1), help="Bad choice: {error_msg}", type=int,
            required=True, default=0, location='form'
        )  # 是否为容器
        parser.add_argument(
            "checkFileName", help="仅检测文件名称", type=int, default=0, location='form'
        )  # 加入检测文件名称参数, 配合前端分片上传前检测文件参数是否正常
        params = parser.parse_args()

        name = params["filename"]
        os_type = params["os_type"]
        task_id = params["identifier"]
        chunk = params["chunkNumber"]
        is_done = params["is_done"]
        ext = params["ext"]
        check_filename = params["checkFileName"]
        is_container = params["is_container"]

        if not name:
            abort(400, message="镜像名称不可为空!")

        if ext.lower() not in ['vmdk', 'iso', 'qcow2', "raw"]:
            abort(400, message="仅支持 ['vmdk', 'iso', 'qcow2', 'raw'] 格式文件!")

        if is_container and os_type != "linux":
            abort(400, message="容器镜像操作系统必须是linux")

        file_path = os.path.join(IMAGE_BASE_PATH, f"{name}.{ext}")

        if check_filename:
            if os.path.exists(file_path):
                abort(400, message="文件名重复请重新输入!")
            else:
                return {"code": 200, "message": "名称无异常!"}

        if not file and not is_done:
            abort(400, message="未选择文件!")

        chunk_base_file_path = os.path.join(IMAGE_BASE_PATH, task_id)
        if not is_done:
            # 创建分片保存路径
            if chunk == 1:
                # 首次分片上传检查文件名是否重复
                if os.path.exists(file_path):
                    abort(400, message="文件名重复请重新输入!")
                os.makedirs(chunk_base_file_path, exist_ok=True)
            try:
                file.save(os.path.join(chunk_base_file_path, str(chunk)))
            except Exception as e:
                abort(400, message=f"{e}, 保存分片文件失败!")
        else:
            # 所有分片上传完成
            chunk = 1
            try:
                with open(file_path, "wb") as f:
                    while True:
                        chunk_file_path = os.path.join(chunk_base_file_path, str(chunk))
                        try:
                            chunk_f = open(chunk_file_path, "rb")
                            f.write(chunk_f.read())
                            chunk_f.close()
                        except OSError:
                            break
                        chunk += 1
                        os.remove(chunk_file_path)  # 删除分片
                os.rmdir(chunk_base_file_path)
            except:  # noqa
                os.remove(file_path)
                abort(400, message="文件异常, 请重新上传!")
            else:
                _image = Image()
                _image.image_name = name
                _image.upload_path = file_path
                _image.os_type = os_type
                if is_container:
                    _image.container_format = ContainerFormat.docker
                db.session.add(_image)
                db.session.commit()
        return {"code": 200, "message": "保存成功!"}


class VmImagesUpdateDestroyAPI(AdminValidators):
    """
    post: 调用openstack create_image
    put: 修改 vm_image
    delete: 删除镜像
    """
    
    @property
    def load_cloud_object(self):
        return WebHookManager()

    def _get_openstack_manager(self):
        return OpenstackManager(
            current_app.config["OPENSTACK"], project_id=self.user.project_id
        )

    @staticmethod
    def _get_or_error(vm_image_id) -> Image:
        vm_image = Image.query.filter_by(id=vm_image_id).first()
        if not vm_image:
            abort(400, message="对象未找到!")
        if vm_image.status != '2':
            abort(400, message="仅可操作性用户自定义镜像!")
        return vm_image

    @classmethod
    def create_container_image(cls, params: dict, vm_image: Image):
        # # 格式为: 项目类型+镜像名+镜像类型+实例类型+内网ip(自上传的暂时获取不到)
        image_full_name = f'{vm_image.status}_{vm_image.image_name}_{params["type"]}_1-2-10'
        image = ops.glance_operate.create_container_image(
            image_full_name,
            vm_image.upload_path,
            setup_cpu=1,
            setup_ram=2048,
        )
        vm_image.image_full_name = image_full_name
        vm_image.image_id = image.id
        vm_image.image_type = params["type"]
        vm_image.image_explain = params["explain"]

        db.session.add(vm_image)

    def create_kvm_image(self, params: dict, vm_image: Image):
        flavor_name = params.get("flavor_id", "")
        # 格式为: 项目类型+镜像名+镜像类型+实例类型+内网ip(自上传的暂时获取不到)
        image_full_name = f'{vm_image.status}_{vm_image.image_name}_{params["type"]}_{flavor_name}_'
        
        os_type = ["Linux", "Windows", "Other", "Paravirtualization", "WindowsVirtio"]
        flavor_id = params["flavor_id"]
        vm_image.image_full_name = image_full_name
        vm_image.image_id = Tools.get_uuid4_no_line()
        vm_image.image_type = params["type"]
        vm_image.flavor_id = flavor_id
        vm_image.image_explain = params["explain"]

        db.session.add(vm_image)
        upload_path = vm_image.upload_path
        if vm_image.os_type.capitalize() not  in os_type:
            abort(400, message=f"镜像系统格式:{vm_image.os_type}不支持上传")
        image_type = upload_path.split(".")[-1]
        # 拼接url
        image_url = current_app.config["ZSTACK_CLOUD"]["image_file_url"] + upload_path.replace(IMAGE_BASE_PATH, "")
        self.load_cloud_object.create_image(
            image_full_name, 
            image_url,  
            vm_image.image_id,
            image_type,
            vm_image.os_type.capitalize())
        

    def post(self, vm_image_id):
        parser = reqparse.RequestParser(bundle_errors=False)
        parser.add_argument("flavor_id", help="flavor_id未提供!", required=True)
        parser.add_argument("type", help="镜像类型未提供!", required=True)
        parser.add_argument("explain", help="镜像说明未提供!", required=False)
        params = parser.parse_args()
        vm_image = self._get_or_error(vm_image_id)
        # 镜像id不为空说明已调用过create_image
        if vm_image.image_id:
            abort(400, message="对象已包含镜像!")

        try:
            if vm_image.container_format != ContainerFormat.docker:
                self.create_kvm_image(params, vm_image)
            else:
                self.create_container_image(params, vm_image)
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            current_app.logger.exception('创建镜像失败--', e)
            abort(400, message="创建失败!")

        return {"code": 200, "message": "创建成功!"}

    def put(self, vm_image_id):
        parser = reqparse.RequestParser(bundle_errors=False)
        parser.add_argument("flavor_id", help="flavor_id未提供!", required=True)
        parser.add_argument("type", help="镜像类型未提供!", required=True)
        parser.add_argument("explain")
        params = parser.parse_args()
        vm_image = self._get_or_error(vm_image_id)
        vm_image.image_type = params["type"]
        vm_image.flavor_id = params["flavor_id"]
        vm_image.flavor_id = params["explain"]
        with db.auto_commit():
            db.session.commit()
        return {"code": 200, "message": "修改成功!"}

    def delete(self, vm_image_id):
        vm_image = self._get_or_error(vm_image_id)

        # 尝试删除镜像文件
        try:
            if vm_image.container_format == "docker":
                ops.glance_operate.delete_image(vm_image.image_id)
            elif vm_image.image_id:
                del_result = self.load_cloud_object.del_image_destroy(vm_image.image_id)
                if del_result and 'error' not in del_result.keys():
                    db.session.delete(vm_image)
                    db.session.commit()
                else:
                    abort(400, message="删除失败!")
            else:
                db.session.delete(vm_image)
                db.session.commit()
            # 删除镜像文件
            if vm_image.upload_path:
                if os.path.exists(vm_image.upload_path):
                    os.remove(vm_image.upload_path)
        except Exception as e:
            db.session.rollback()
            current_app.logger.exception(e)
        return {"code": 200, "message": "删除成功!"}
